<!--
 * @Author: Baishui
 * @Date: 2021-09-28 20:38:11
 * @LastEditors: Baishui
 * @LastEditTime: 2021-10-08 11:11:02
 * @Description: file content
-->
<template>
  <svg width="1000" height="500" id="custom-tree" />
</template>

<script lang="ts">
  import { defineComponent, reactive, toRefs, onMounted } from 'vue'
  import { reqChartCustomTree } from '@/api/chart'
  import * as d3 from 'd3'
  import { isEmpty } from 'lodash-es'

  export default defineComponent({
    name: 'CustomTree',
    components: {},
    setup() {
      const state = reactive({})
      onMounted(async () => {
        const res = await reqChartCustomTree()
        initSvg()
        renderTop(res.top)
        renderBottom(res.bottom)
      })
      let gTop: any = null
      let gBottom: any = null

      function initSvg() {
        const svg = d3.select('#custom-tree')
        const width = +svg.attr('width')
        const height = +svg.attr('height')
        const margin = { top: 50, left: 20, bottom: 50, right: 150 }
        innerWidth = width - margin.left - margin.right
        innerHeight = height - margin.top - margin.bottom

        gTop = svg
          .append('g')
          .attr('class', 'top-group')
          .attr('transform', `translate(${margin.left}, ${innerHeight / 2})`)

        gBottom = svg
          .append('g')
          .attr('class', 'bottom-group')
          .attr('transform', `translate(${margin.left}, ${innerHeight / 2})`)

        // 箭头
        const defs: any = svg.append('defs')
        const arrowMarker = defs
          .append('marker')
          .attr('id', 'arrow')
          .attr('markerUnits', 'strokeWidth')
          .attr('markerWidth', 12)
          .attr('markerHeight', 12)
          .attr('viewBox', '0 0 12 12')
          .attr('refX', 6)
          .attr('refY', 6)
          .attr('orient', 'auto')

        arrowMarker.append('path').attr('d', 'M2,2 L10,6 L2,10 L6,6 l2,2').attr('fill', '#8ECBF6')
      }

      function renderTop(data: any) {
        if (isEmpty(data)) return

        let _data = d3.hierarchy(data)

        let root = d3.tree().size([innerWidth + 100, innerHeight / 3])(_data)

        // 线
        gTop
          .append('g')
          .attr('class', 'line-group')
          .selectAll('pth')
          .data(root.links())
          .join('path')
          .attr('fill', 'none')
          .attr('stroke', '#8ECBF6')
          .attr('stroke-width', 1.5)
          // .attr(
          //   'd',
          //   d3
          //     .linkHorizontal() // 贝塞尔曲线 水平切线
          //     // linkHorizontal() // 垂直切线
          //     .x((d: any) => d.y) // 左右排列
          //     .y((d: any) => d.x)
          // )
          // .attr('d', (d: any) => {
          //   const { source, target } = d
          //   const path = d3.path()
          //   path.moveTo(target.x, -target.y)
          //   path.lineTo(target.x, -target.y + 50)
          //   path.lineTo(source.x, -target.y + 50)
          //   path.lineTo(source.x, -target.y - 70)
          //   return path.toString()
          // })
          .attr('d', (d: any) => {
            const { source, target } = d
            return `M${target.x}, ${-target.y}
                    L${target.x}, ${-target.y + 50}
                    L${source.x}, ${-target.y + 50}
                    L${source.x}, ${-source.y - 70}
                  `
          })
          .attr('marker-end', 'url(#arrow)')

        // 公司名称
        const nameGroup = gTop.append('g').attr('class', 'comName-group')
        nameGroup
          .selectAll('text')
          .data(root.descendants().filter((item) => item.depth))
          .join('text')
          .attr('text-anchor', 'middle')
          .style('fill', 'rgba(31,51,115,0.65')
          .attr('x', (d: any) => d.x)
          .attr('y', (d: any) => -d.y - 10)
          .text(function (d: any) {
            setTimeout(() => {
              const bbox = this.getBBox()
              nameGroup
                .insert('rect')
                .attr('x', bbox.x - 10)
                .attr('y', bbox.y - 5)
                .attr('width', bbox.width + 20)
                .attr('height', 32)
                .attr('rx', 2)
                .attr('ry', 2)
                .attr('fill', '#D1E8FF')
                .lower()
            }, 10)
            return d.data.name
          })

        // 股份占比
        const percentGroup = gTop.append('g').attr('class', 'percent-group')
        percentGroup
          .selectAll('text')
          .data(root.descendants().filter((item) => item.depth))
          .join('text')
          .attr('text-anchor', 'middle')
          .style('fill', '#fff')
          .attr('x', (d: any) => d.x)
          .attr('y', (d: any) => -d.y + 35)
          .text(function (d: any) {
            setTimeout(() => {
              const bbox = this.getBBox()
              nameGroup
                .insert('rect')
                .attr('x', bbox.x - 10)
                .attr('y', bbox.y - 3)
                .attr('width', bbox.width + 20)
                .attr('height', 22)
                .attr('rx', 2)
                .attr('ry', 2)
                .attr('fill', '#8D6DFF')
                .lower()
            }, 10)
            return d.data.value
          })
      }

      function renderBottom(data: any) {
        if (isEmpty(data)) return

        let _data = d3.hierarchy(data)

        let root = d3.tree().size([innerWidth + 100, innerHeight / 3])(_data)

        // 根节点与子节点之间的连线
        gBottom
          .append('g')
          .attr('class', 'line-group')
          .selectAll('path')
          .data(root.links())
          .join('path')
          .attr('fill', 'none')
          .attr('stroke', '#8ECBF6')
          .attr('stroke-width', 1.5)
          .attr('d', (d: any) => {
            const { source, target } = d
            return `M${source.x}, ${source.y}
                    L${source.x}, ${source.y + 60}
                    L${target.x}, ${source.y + 60}
                    L${target.x}, ${target.y}
                  `
          })
          .attr('transform', 'translate(0, -10)')
          .attr('marker-end', 'url(#arrow)')

        // 占比值
        const percentGroup = gBottom.append('g').attr('class', 'percent-group2')
        percentGroup
          .selectAll('text')
          .data(root.descendants().filter((item) => item.depth))
          .join('text')
          .attr('text-anchor', 'middle')
          .style('fill', '#fff')
          .attr('x', (d: any) => d.x)
          .attr('y', (d: any) => d.y - 40)
          .text(function (d: any) {
            setTimeout(() => {
              const bbox = this.getBBox()
              percentGroup
                .insert('rect')
                .attr('x', bbox.x - 10)
                .attr('y', bbox.y - 3)
                .attr('width', bbox.width + 20)
                .attr('height', 22)
                .attr('rx', 2)
                .attr('ry', 2)
                .attr('fill', '#8D6DFF')
                .lower()
            }, 10)
            return d.data.value
          })

        // 节点的盒子
        gBottom
          .append('g')
          .attr('class', 'path-box')
          .selectAll('path')
          .data(root.descendants())
          .enter()
          .append('path')
          .attr('d', (d: any, i: number) => {
            const { x, y } = d
            const path = d3.path()

            if (i === 0) {
              path.moveTo(x, y)
              path.lineTo(x + 250, y)
              path.lineTo(x + 250, y + 60)
              path.lineTo(x, y + 60)
              path.lineTo(x, y)
            } else {
              path.moveTo(x, y)
              path.lineTo(x + 120, y)
              path.lineTo(x + 120, y + 60)
              path.lineTo(x, y + 60)
              path.lineTo(x, y)
            }
            return path.toString()
          })
          .attr(
            'transform',
            (_, i: number) => `translate(${i !== 0 ? -60 : -125}, ${i !== 0 ? 0 : -60})`
          )
          .attr('fill', '#fff')
          .attr('stroke', (_, i: number) => (i !== 0 ? '#258AFF' : '#8D6DFF'))
          .attr('stroke-width', 0.5)

        // 根节点text

        const rootNode: any = root.descendants().filter((item) => item.depth === 0)[0]
        const rootText = gBottom.append('g').attr('class', 'root-text')

        rootText
          .append('text')
          .attr('class', 'comp-name')
          .attr('text-anchor', 'middle')
          .attr('font-size', 16)
          .attr('font-weight', 700)
          .attr('fill', 'rgba(0,0,0,0.8')
          .attr('x', rootNode.x)
          .attr('y', rootNode.y - 40)
          .text(() => rootNode.data.name)

        rootText
          .append('text')
          .attr('class', 'comp-name')
          .attr('text-anchor', 'middle')
          .attr('font-size', 12)
          .attr('fill', 'rgba(120,107,191,0.65')
          .attr('x', rootNode.x)
          .attr('y', rootNode.y - 10)
          .text(function () {
            setTimeout(() => {
              const bbox = this.getBBox()
              rootText
                .insert('rect')
                .attr('x', bbox.x - 65)
                .attr('y', bbox.y)
                .attr('width', 240)
                .attr('height', bbox.height)
                .attr('fill', '#A38FFF')
                .attr('opacity', 0.2)
            }, 10)
            return '如下为企业控股单位'
          })

        gBottom
          .append('g')
          .attr('class', 'leaf-node-text')
          .selectAll('text')
          .data(root.descendants().filter((item) => item.depth > 0))
          .join('text')
          .attr('class', 'ctrl-comp-name')
          .attr('text-anchor', 'middle')
          .attr('font-size', 14)
          .attr('x', (d: any) => d.x)
          .attr('y', (d: any) => d.y + 20)
          .selectAll('tspan')
          .data((d: any) => {
            let s = d.data.name
            let str: string[] = []
            try {
              str = [s.substr(0, s.length / 2), s.substr(s.length / 2)]
            } catch (error) {}
            return str.map((item) => ({ name: item, x: d.x, y: d.y }))
          })
          .enter()
          .append('tspan')
          .attr('x', (d: any) => d.x)
          .attr('dy', '1em')
          .text((d: any) => d.name)
      }
      return {
        ...toRefs(state),
      }
    },
  })
</script>
<style lang="less" scoped>
  .wrapper {
  }
</style>
